/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
 
This file is part of Quake III Arena source code.
 
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
 
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
//
// cg_weapons.c -- events and effects dealing with weapons
#include "cg_local.h"

/*
==========================
CG_RailTrail
==========================
*/
void CG_RailTrail (clientInfo_t *ci, vec3_t start, vec3_t end)
{
    vec3_t axis[36], move, move2, next_move, vec, temp;
    float  len;
    int    i, j, skip;

    localEntity_t *le;
    refEntity_t   *re;

#define RADIUS   4
#define ROTATION 1
#define SPACING  5

//    start[2] -= 4;
    VectorCopy (start, move);
    VectorSubtract (end, start, vec);
    len = VectorNormalize (vec);
    PerpendicularVector(temp, vec);
    for (i = 0 ; i < 36; i++)
    {
        RotatePointAroundVector(axis[i], vec, temp, i * 10);//banshee 2.4 was 10
    }

    le = CG_AllocLocalEntity();
    re = &le->refEntity;

    le->leType = LE_FADE_RGB;
    le->startTime = cg.time;
    le->endTime = cg.time + cg_railTrailTime->value;
    le->lifeRate = 1.0 / (le->endTime - le->startTime);

    re->shaderTime = cg.time / 1000.0f;
    re->reType = RT_RAIL_CORE;
    re->customShader = cgs.media.railCoreShader;

    VectorCopy(start, re->origin);
    VectorCopy(end, re->oldorigin);

    re->shaderRGBA[0] = ci->color1[0] * 255;
    re->shaderRGBA[1] = ci->color1[1] * 255;
    re->shaderRGBA[2] = ci->color1[2] * 255;
    re->shaderRGBA[3] = 255;

    le->color[0] = ci->color1[0] * 0.75;
    le->color[1] = ci->color1[1] * 0.75;
    le->color[2] = ci->color1[2] * 0.75;
    le->color[3] = 1.0f;

    AxisClear( re->axis );

    VectorMA(move, 20, vec, move);
    VectorCopy(move, next_move);
    VectorScale (vec, SPACING, vec);

    if (cg_oldRail->integer != 0)
    {
        // nudge down a bit so it isn't exactly in center
        re->origin[2] -= 8;
        re->oldorigin[2] -= 8;
        return;
    }
    skip = -1;

    j = 18;
    for (i = 0; i < len; i += SPACING)
    {
        if (i != skip)
        {
            skip = i + SPACING;
            le = CG_AllocLocalEntity();
            re = &le->refEntity;
            le->leFlags = LEF_PUFF_DONT_SCALE;
            le->leType = LE_MOVE_SCALE_FADE;
            le->startTime = cg.time;
            le->endTime = cg.time + (i>>1) + 600;
            le->lifeRate = 1.0 / (le->endTime - le->startTime);

            re->shaderTime = cg.time / 1000.0f;
            re->reType = RT_SPRITE;
            re->radius = 1.1f;
            re->customShader = cgs.media.railRingsShader;

            re->shaderRGBA[0] = ci->color2[0] * 255;
            re->shaderRGBA[1] = ci->color2[1] * 255;
            re->shaderRGBA[2] = ci->color2[2] * 255;
            re->shaderRGBA[3] = 255;

            le->color[0] = ci->color2[0] * 0.75;
            le->color[1] = ci->color2[1] * 0.75;
            le->color[2] = ci->color2[2] * 0.75;
            le->color[3] = 1.0f;

            le->pos.trType = TR_LINEAR;
            le->pos.trTime = cg.time;

            VectorCopy( move, move2);
            VectorMA(move2, RADIUS , axis[j], move2);
            VectorCopy(move2, le->pos.trBase);

            le->pos.trDelta[0] = axis[j][0]*6;
            le->pos.trDelta[1] = axis[j][1]*6;
            le->pos.trDelta[2] = axis[j][2]*6;
        }

        VectorAdd (move, vec, move);

        j = j + ROTATION < 36 ? j + ROTATION : (j + ROTATION) % 36;
    }
}

/*
==========================
CG_RocketTrail
==========================
*/
#if 0
static void CG_RocketTrail( centity_t *ent, const weaponInfo_t *wi )
{
    int		step;
    vec3_t	origin, lastPos;
    int		t;
    int		startTime, contents;
    int		lastContents;
    entityState_t	*es;
    vec3_t	up;
    localEntity_t	*smoke;

    if ( cg_noProjectileTrail->integer )
    {
        return;
    }

    up[0] = 0;
    up[1] = 0;
    up[2] = 0;

    step = 50;

    es = &ent->currentState;
    startTime = ent->trailTime;
    t = step * ( (startTime + step) / step );

    BG_EvaluateTrajectory( &es->pos, cg.time, origin );
    contents = CG_PointContents( origin, -1 );

    // if object (e.g. grenade) is stationary, don't toss up smoke
    if ( es->pos.trType == TR_STATIONARY )
    {
        ent->trailTime = cg.time;
        return;
    }

    BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos );
    lastContents = CG_PointContents( lastPos, -1 );

    ent->trailTime = cg.time;

    if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) )
    {
        if ( contents & lastContents & CONTENTS_WATER )
        {
            CG_BubbleTrail( lastPos, origin, 8 );
        }
        return;
    }

    for ( ; t <= ent->trailTime ; t += step )
    {
        BG_EvaluateTrajectory( &es->pos, t, lastPos );

        smoke = CG_SmokePuff( lastPos, up,
                              wi->trailRadius,
                              1, 1, 1, 0.33f,
                              wi->wiTrailTime,
                              t,
                              0,
                              0,
                              cgs.media.smokePuffShader );
        // use the optimized local entity add
        smoke->leType = LE_SCALE_FADE;
    }
}

/*
==========================
CG_NailTrail
==========================
*/
static void CG_PlasmaTrail( centity_t *cent, const weaponInfo_t *wi )
{
    localEntity_t	*le;
    refEntity_t		*re;
    entityState_t	*es;
    vec3_t			velocity, xvelocity, origin;
    vec3_t			offset, xoffset;
    vec3_t			v[3];
    int				t, startTime, step;

    float	waterScale = 1.0f;

    if ( cg_noProjectileTrail->integer || cg_oldPlasma->integer )
    {
        return;
    }

    step = 50;

    es = &cent->currentState;
    startTime = cent->trailTime;
    t = step * ( (startTime + step) / step );

    BG_EvaluateTrajectory( &es->pos, cg.time, origin );

    le = CG_AllocLocalEntity();
    re = &le->refEntity;

    velocity[0] = 60 - 120 * crandom();
    velocity[1] = 40 - 80 * crandom();
    velocity[2] = 100 - 200 * crandom();

    le->leType = LE_MOVE_SCALE_FADE;
    le->leFlags = LEF_TUMBLE;
    le->leBounceSoundType = LEBS_NONE;
    le->leMarkType = LEMT_NONE;

    le->startTime = cg.time;
    le->endTime = le->startTime + 600;

    le->pos.trType = TR_GRAVITY;
    le->pos.trTime = cg.time;

    AnglesToAxis( cent->lerpAngles, v );

    offset[0] = 2;
    offset[1] = 2;
    offset[2] = 2;

    xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0];
    xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1];
    xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2];

    VectorAdd( origin, xoffset, re->origin );
    VectorCopy( re->origin, le->pos.trBase );

    if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER )
    {
        waterScale = 0.10f;
    }

    xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0];
    xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1];
    xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2];
    VectorScale( xvelocity, waterScale, le->pos.trDelta );

    AxisCopy( axisDefault, re->axis );
    re->shaderTime = cg.time / 1000.0f;
    re->reType = RT_SPRITE;
    re->radius = 0.25f;
    re->customShader = cgs.media.railRingsShader;
    le->bounceFactor = 0.3f;

    re->shaderRGBA[0] = wi->flashDlightColor[0] * 63;
    re->shaderRGBA[1] = wi->flashDlightColor[1] * 63;
    re->shaderRGBA[2] = wi->flashDlightColor[2] * 63;
    re->shaderRGBA[3] = 63;

    le->color[0] = wi->flashDlightColor[0] * 0.2;
    le->color[1] = wi->flashDlightColor[1] * 0.2;
    le->color[2] = wi->flashDlightColor[2] * 0.2;
    le->color[3] = 0.25f;

    le->angles.trType = TR_LINEAR;
    le->angles.trTime = cg.time;
    le->angles.trBase[0] = rand()&31;
    le->angles.trBase[1] = rand()&31;
    le->angles.trBase[2] = rand()&31;
    le->angles.trDelta[0] = 1;
    le->angles.trDelta[1] = 0.5;
    le->angles.trDelta[2] = 0;
}

/*
==========================
CG_GrappleTrail
==========================
*/
void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi )
{
    vec3_t	origin;
    entityState_t	*es;
    vec3_t			forward, up;
    refEntity_t		beam;

    es = &ent->currentState;

    BG_EvaluateTrajectory( &es->pos, cg.time, origin );
    ent->trailTime = cg.time;

    memset( &beam, 0, sizeof( beam ) );
    //FIXME adjust for muzzle position
    VectorCopy ( cg_entities[ ent->currentState.otherEntityNum ].lerpOrigin, beam.origin );
    beam.origin[2] += 26;
    AngleVectors( cg_entities[ ent->currentState.otherEntityNum ].lerpAngles, forward, NULL, up );
    VectorMA( beam.origin, -6, up, beam.origin );
    VectorCopy( origin, beam.oldorigin );

    if (Distance( beam.origin, beam.oldorigin ) < 64 )
        return; // Don't draw if close

    beam.reType = RT_LIGHTNING;
    beam.customShader = cgs.media.lightningShader;

    AxisClear( beam.axis );
    beam.shaderRGBA[0] = 0xff;
    beam.shaderRGBA[1] = 0xff;
    beam.shaderRGBA[2] = 0xff;
    beam.shaderRGBA[3] = 0xff;
    RE_AddRefEntityToScene( &beam );
}

/*
==========================
CG_GrenadeTrail
==========================
*/
static void CG_GrenadeTrail( centity_t *ent, const weaponInfo_t *wi )
{
    CG_RocketTrail( ent, wi );
}
#endif

/*
=================
CG_RegisterWeapon
 
The server says this item is used on this level
=================
*/
void CG_RegisterWeapon( int weaponNum )
{
#if 0
    weaponInfo_t	*weaponInfo;
    gitem_t			*item, *ammo;
    char			path[MAX_QPATH];
    vec3_t			mins, maxs;
    int				i;

    weaponInfo = &cg_weapons[weaponNum];

    if ( weaponNum == 0 )
    {
        return;
    }

    if ( weaponInfo->registered )
    {
        return;
    }

    memset( weaponInfo, 0, sizeof( *weaponInfo ) );
    weaponInfo->registered = qtrue;

    for ( item = bg_itemlist + 1 ; item->classname ; item++ )
    {
        if ( item->giType == IT_POWERUP && item->giTag >= PW_GAUNTLET && item->giTag <= PW_BFG)
        {
            weaponInfo->item = item;
            break;
        }
    }
    if ( !item->classname )
    {
        Com_Error( ERR_DROP,  "Couldn't find weapon %i", weaponNum );
    }
    CG_RegisterItemVisuals( item - bg_itemlist );

    // load cmodel before model so filecache works
    weaponInfo->weaponModel = RE_RegisterModel( item->world_model[0] );

    // calc midpoint for rotation
    R_ModelBounds( weaponInfo->weaponModel, mins, maxs );
    for ( i = 0 ; i < 3 ; i++ )
    {
        weaponInfo->weaponMidpoint[i] = mins[i] + 0.5 * ( maxs[i] - mins[i] );
    }

    weaponInfo->weaponIcon = RE_RegisterShader( item->icon );
    weaponInfo->ammoIcon = RE_RegisterShader( item->icon );

    for ( ammo = bg_itemlist + 1 ; ammo->classname ; ammo++ )
    {
        if ( ammo->giType == IT_AMMO && ammo->giTag == weaponNum )
        {
            break;
        }
    }
    if ( ammo->classname && ammo->world_model[0] )
    {
        weaponInfo->ammoModel = RE_RegisterModel( ammo->world_model[0] );
    }

    strcpy( path, item->world_model[0] );
    COM_StripExtension( path, path );
    strcat( path, "_flash.md3" );
    weaponInfo->flashModel = RE_RegisterModel( path );

    strcpy( path, item->world_model[0] );
    COM_StripExtension( path, path );
    strcat( path, "_barrel.md3" );
    weaponInfo->barrelModel = RE_RegisterModel( path );

    strcpy( path, item->world_model[0] );
    COM_StripExtension( path, path );
    strcat( path, "_hand.md3" );
    weaponInfo->handsModel = RE_RegisterModel( path );

    if ( !weaponInfo->handsModel )
    {
        weaponInfo->handsModel = RE_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" );
    }

    weaponInfo->loopFireSound = qfalse;

    switch ( weaponNum )
    {
    case WP_MELEE:
        MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f );
        weaponInfo->firingSound = S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse );
        weaponInfo->flashSound[0] = S_RegisterSound( "sound/weapons/melee/fstatck.wav", qfalse );
        break;

/*    case WP_LIGHTNING:
        MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f );
        weaponInfo->readySound = S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse );
        weaponInfo->firingSound = S_RegisterSound( "sound/weapons/lightning/lg_hum.wav", qfalse );

        weaponInfo->flashSound[0] = S_RegisterSound( "sound/weapons/lightning/lg_fire.wav", qfalse );
        cgs.media.lightningShader = RE_RegisterShader( "lightningBoltNew");
        cgs.media.lightningExplosionModel = RE_RegisterModel( "models/weaphits/crackle.md3" );
        cgs.media.sfx_lghit1 = S_RegisterSound( "sound/weapons/lightning/lg_hit.wav", qfalse );
        cgs.media.sfx_lghit2 = S_RegisterSound( "sound/weapons/lightning/lg_hit2.wav", qfalse );
        cgs.media.sfx_lghit3 = S_RegisterSound( "sound/weapons/lightning/lg_hit3.wav", qfalse );

        break;

    case WP_GRAPPLING_HOOK:
        MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f );
        weaponInfo->missileModel = RE_RegisterModel( "models/ammo/rocket/rocket.md3" );
        weaponInfo->missileTrailFunc = CG_GrappleTrail;
        weaponInfo->missileDlight = 200;
        weaponInfo->wiTrailTime = 2000;
        weaponInfo->trailRadius = 64;
        MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 );
        weaponInfo->readySound = S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse );
        weaponInfo->firingSound = S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse );
        break;

    case WP_MACHINEGUN:
        MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 );
        weaponInfo->flashSound[0] = S_RegisterSound( "sound/weapons/machinegun/machgf1b.wav", qfalse );
        weaponInfo->flashSound[1] = S_RegisterSound( "sound/weapons/machinegun/machgf2b.wav", qfalse );
        weaponInfo->flashSound[2] = S_RegisterSound( "sound/weapons/machinegun/machgf3b.wav", qfalse );
        weaponInfo->flashSound[3] = S_RegisterSound( "sound/weapons/machinegun/machgf4b.wav", qfalse );
        weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
        cgs.media.bulletExplosionShader = RE_RegisterShader( "bulletExplosion" );
        break;

    case WP_SHOTGUN:
        MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 );
        weaponInfo->flashSound[0] = S_RegisterSound( "sound/weapons/shotgun/sshotf1b.wav", qfalse );
        weaponInfo->ejectBrassFunc = CG_ShotgunEjectBrass;
        break;

    case WP_ROCKET_LAUNCHER:
        weaponInfo->missileModel = RE_RegisterModel( "models/ammo/rocket/rocket.md3" );
        weaponInfo->missileSound = S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse );
        weaponInfo->missileTrailFunc = CG_RocketTrail;
        weaponInfo->missileDlight = 200;
        weaponInfo->wiTrailTime = 2000;
        weaponInfo->trailRadius = 64;

        MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 );
        MAKERGB( weaponInfo->flashDlightColor, 1, 0.75f, 0 );

        weaponInfo->flashSound[0] = S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse );
        cgs.media.rocketExplosionShader = RE_RegisterShader( "rocketExplosion" );
        break;

    case WP_GRENADE_LAUNCHER:
        weaponInfo->missileModel = RE_RegisterModel( "models/ammo/grenade1.md3" );
        weaponInfo->missileTrailFunc = CG_GrenadeTrail;
        weaponInfo->wiTrailTime = 700;
        weaponInfo->trailRadius = 32;
        MAKERGB( weaponInfo->flashDlightColor, 1, 0.70f, 0 );
        weaponInfo->flashSound[0] = S_RegisterSound( "sound/weapons/grenade/grenlf1a.wav", qfalse );
        cgs.media.grenadeExplosionShader = RE_RegisterShader( "grenadeExplosion" );
        break;

    case WP_PLASMAGUN:
        weaponInfo->missileTrailFunc = CG_PlasmaTrail;
        weaponInfo->missileSound = S_RegisterSound( "sound/weapons/plasma/lasfly.wav", qfalse );
        MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f );
        weaponInfo->flashSound[0] = S_RegisterSound( "sound/weapons/plasma/hyprbf1a.wav", qfalse );
        cgs.media.plasmaExplosionShader = RE_RegisterShader( "plasmaExplosion" );
        cgs.media.railRingsShader = RE_RegisterShader( "railDisc" );
        break;*/

    case WP_RANGE:
        weaponInfo->readySound = S_RegisterSound( "sound/weapons/railgun/rg_hum.wav", qfalse );
        MAKERGB( weaponInfo->flashDlightColor, 1, 0.5f, 0 );
        weaponInfo->flashSound[0] = S_RegisterSound( "sound/weapons/railgun/railgf1a.wav", qfalse );
        cgs.media.railExplosionShader = RE_RegisterShader( "railExplosion" );
        cgs.media.railRingsShader = RE_RegisterShader( "railDisc" );
        cgs.media.railCoreShader = RE_RegisterShader( "railCore" );
        break;

    case WP_SPECIAL:
        weaponInfo->readySound = S_RegisterSound( "sound/weapons/bfg/bfg_hum.wav", qfalse );
        MAKERGB( weaponInfo->flashDlightColor, 1, 0.7f, 1 );
        weaponInfo->flashSound[0] = S_RegisterSound( "sound/weapons/bfg/bfg_fire.wav", qfalse );
        cgs.media.bfgExplosionShader = RE_RegisterShader( "bfgExplosion" );
        weaponInfo->missileModel = RE_RegisterModel( "models/weaphits/bfg.md3" );
        weaponInfo->missileSound = S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse );
        break;

    default:
        MAKERGB( weaponInfo->flashDlightColor, 1, 1, 1 );
        weaponInfo->flashSound[0] = S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse );
        break;
    }
#endif
}

/*
=================
CG_RegisterItemVisuals
 
The server says this item is used on this level
=================
*/
void CG_RegisterItemVisuals( int itemNum )
{
    itemInfo_t		*itemInfo;
    gitem_t			*item;

    if ( itemNum < 0 || itemNum >= bg_numItems )
    {
        Com_Error( ERR_DROP,  "CG_RegisterItemVisuals: itemNum %d out of range [0-%d]", itemNum, bg_numItems-1 );
    }

    itemInfo = &cg_items[ itemNum ];
    if ( itemInfo->registered )
    {
        return;
    }

    item = &bg_itemlist[ itemNum ];

    memset( itemInfo, 0, sizeof( &itemInfo ) );
    itemInfo->registered = qtrue;

    itemInfo->models[0] = RE_RegisterModel( item->world_model[0] );

    itemInfo->icon = RE_RegisterShader( item->icon );

//    if ( item->giType == IT_WEAPON )
//    {
//        CG_RegisterWeapon( item->giTag );
//    }

    //
    // powerups have an accompanying ring or sphere
    //
    if ( item->giType == IT_POWERUP || item->giType == IT_HEALTH ||
            item->giType == IT_ARMOR || item->giType == IT_HOLDABLE )
    {
        if ( item->world_model[1] )
        {
            itemInfo->models[1] = RE_RegisterModel( item->world_model[1] );
        }
    }
}


/*
========================================================================================
 
VIEW WEAPON
 
========================================================================================
*/

/*
 
static void CG_LightningBolt( centity_t *cent, vec3_t origin ) {
	trace_t		trace;
	refEntity_t		beam;
	vec3_t			forward;
	vec3_t			muzzlePoint, endPoint;
 
	if ( cent->currentState.weapon != WP_LIGHTNING ) {
		return;
	}
 
	memset( &beam, 0, sizeof( beam ) );
 
	// find muzzle point for this frame
	VectorCopy( cent->lerpOrigin, muzzlePoint );
	AngleVectors( cent->lerpAngles, forward, NULL, NULL );
 
	// FIXME: crouch
	muzzlePoint[2] += DEFAULT_VIEWHEIGHT;
 
	VectorMA( muzzlePoint, 14, forward, muzzlePoint );
 
	// project forward by the lightning range
	VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint );
 
	// see if it hit a wall
	CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint, 
		cent->currentState.number, MASK_SHOT );
 
	// this is the endpoint
	VectorCopy( trace.endpos, beam.oldorigin );
 
	// use the provided origin, even though it may be slightly
	// different than the muzzle origin
	VectorCopy( origin, beam.origin );
 
	beam.reType = RT_LIGHTNING;
	beam.customShader = cgs.media.lightningShader;
	RE_AddRefEntityToScene( &beam );
 
	// add the impact flare if it hit something
	if ( trace.fraction < 1.0 ) {
		vec3_t	angles;
		vec3_t	dir;
 
		VectorSubtract( beam.oldorigin, beam.origin, dir );
		VectorNormalize( dir );
 
		memset( &beam, 0, sizeof( beam ) );
		beam.hModel = cgs.media.lightningExplosionModel;
 
		VectorMA( trace.endpos, -16, dir, beam.origin );
 
		// make a random orientation
		angles[0] = rand() % 360;
		angles[1] = rand() % 360;
		angles[2] = rand() % 360;
		AnglesToAxis( angles, beam.axis );
		RE_AddRefEntityToScene( &beam );
	}
}
*/

/*
=============
CG_AddPlayerWeapon
 
Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL)
The main player will have this called for BOTH cases, so effects like light and
sound should only be done on the world model case.
=============
*/
void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team )
{
/*    refEntity_t	gun;
    refEntity_t	barrel;
    refEntity_t	flash;
    vec3_t		angles;
    weapon_t	weaponNum;
    centity_t	*nonPredictedCent;
//	int	col;

    weaponNum = cent->currentState.weapon;

    CG_RegisterWeapon( weaponNum );
    weapon = &cg_weapons[weaponNum];

    // add the weapon
    memset( &gun, 0, sizeof( gun ) );
    VectorCopy( parent->lightingOrigin, gun.lightingOrigin );
    gun.shadowPlane = parent->shadowPlane;
    gun.renderfx = parent->renderfx;

    // set custom shading for railgun refire rate
    if ( ps )
    {
        gun.shaderRGBA[0] = 255;
        gun.shaderRGBA[1] = 255;
        gun.shaderRGBA[2] = 255;
        gun.shaderRGBA[3] = 255;
    }

    gun.hModel = weapon->weaponModel;
    if (!gun.hModel)
    {
        return;
    }

    if ( !ps )
    {
        // add weapon ready sound
        cent->pe.lightningFiring = qfalse;
        if ( ( cent->currentState.eFlags & EF_FIRING ) && weapon->firingSound )
        {
            // lightning gun and guantlet make a different sound when fire is held down
            S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound );
            cent->pe.lightningFiring = qtrue;
        }
        else if ( weapon->readySound )
        {
            S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound );
        }
    }

    CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon");

    CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups );

    // add the spinning barrel
    if ( weapon->barrelModel )
    {
        memset( &barrel, 0, sizeof( barrel ) );
        VectorCopy( parent->lightingOrigin, barrel.lightingOrigin );
        barrel.shadowPlane = parent->shadowPlane;
        barrel.renderfx = parent->renderfx;

        barrel.hModel = weapon->barrelModel;
        angles[YAW] = 0;
        angles[PITCH] = 0;
        angles[ROLL] = CG_MachinegunSpinAngle( cent );
        AnglesToAxis( angles, barrel.axis );

        CG_PositionRotatedEntityOnTag( &barrel, &gun, weapon->weaponModel, "tag_barrel" );

        CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups );
    }

    // make sure we aren't looking at cg.predictedPlayerEntity for LG
    nonPredictedCent = &cg_entities[cent->currentState.clientNum];

    // if the index of the nonPredictedCent is not the same as the clientNum
    // then this is a fake player (like on teh single player podiums), so
    // go ahead and use the cent
    if( ( nonPredictedCent - cg_entities ) != cent->currentState.clientNum )
    {
        nonPredictedCent = cent;
    }

    // add the flash
    memset( &flash, 0, sizeof( flash ) );
    VectorCopy( parent->lightingOrigin, flash.lightingOrigin );
    flash.shadowPlane = parent->shadowPlane;
    flash.renderfx = parent->renderfx;

    flash.hModel = weapon->flashModel;
    if (!flash.hModel)
        return;

    angles[YAW] = 0;
    angles[PITCH] = 0;
    angles[ROLL] = crandom() * 10;
    AnglesToAxis( angles, flash.axis );

    // colorize the railgun blast
    CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash");
    RE_AddRefEntityToScene( &flash );

    if ( ps || cg.renderingThirdPerson ||
            cent->currentState.number != cg.predictedPlayerState.clientNum )
    {
        // add lightning bolt
        CG_LightningBolt( nonPredictedCent, flash.origin );

        // add rail trail
        CG_SpawnRailTrail( cent, flash.origin );

        if ( weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2] )
        {
            RE_AddLightToScene( flash.origin, 300 + (rand()&31), weapon->flashDlightColor[0],
                                    weapon->flashDlightColor[1], weapon->flashDlightColor[2] );
        }
    }*/
}

/*
==============
CG_AddViewWeapon
 
Add the weapon, and flash for the player's view
==============
*/
void CG_AddViewWeapon( playerState_t *ps )
{
/*    refEntity_t	hand;
    centity_t	*cent;
    clientInfo_t	*ci;
    float		fovOffset;
    vec3_t		angles;
    weaponInfo_t	*weapon;

    if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR )
    {
        return;
    }

    if ( ps->pm_type == PM_INTERMISSION )
    {
        return;
    }

    // no gun if in third person view or a camera is active
    //if ( cg.renderingThirdPerson || cg.cameraMode) {
    if ( cg.renderingThirdPerson )
    {
        return;
    }


    // allow the gun to be completely removed
    if ( !cg_drawGun->integer )
    {
        vec3_t		origin;

        if ( cg.predictedPlayerState.eFlags & EF_FIRING )
        {
            // special hack for lightning gun...
            VectorCopy( cg.refdef.vieworg, origin );
            VectorMA( origin, -8, cg.refdef.viewaxis[2], origin );
            CG_LightningBolt( &cg_entities[ps->clientNum], origin );
        }
        return;
    }

    // don't draw if testing a gun model
    if ( cg.testGun )
    {
        return;
    }

    // drop gun lower at higher fov
    if ( cg_fov->integer > 90 )
    {
        fovOffset = -0.2 * ( cg_fov->integer - 90 );
    }
    else
    {
        fovOffset = 0;
    }

    cent = &cg.predictedPlayerEntity;	// &cg_entities[cg.snap->ps.clientNum];
    //CG_RegisterWeapon( ps->weapons[0].index );
    //weapon = &cg_weapons[ ps->weapons[0].index ];

    memset (&hand, 0, sizeof(hand));

    // set up gun position
    CG_CalculateWeaponPosition( hand.origin, angles );

    VectorMA( hand.origin, cg_gun_x->value, cg.refdef.viewaxis[0], hand.origin );
    VectorMA( hand.origin, cg_gun_y->value, cg.refdef.viewaxis[1], hand.origin );
    VectorMA( hand.origin, (cg_gun_z->value+fovOffset), cg.refdef.viewaxis[2], hand.origin );

    AnglesToAxis( angles, hand.axis );

    // map torso animations to weapon animations
    if ( cg_gun_frame->integer )
    {
        // development tool
        hand.frame = hand.oldframe = cg_gun_frame->integer;
        hand.backlerp = 0;
    }
    else
    {
        // get clientinfo for animation map
        ci = &cgs.clientinfo[ cent->currentState.clientNum ];
        hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame );
        hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame );
        hand.backlerp = cent->pe.torso.backlerp;
    }

    hand.hModel = weapon->handsModel;
    hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT;

    // add everything onto the hand
    CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity, ps->persistant[PERS_TEAM] );*/
}

/*
==============================================================================
 
WEAPON SELECTION
 
==============================================================================
*/

/*
===================
CG_DrawWeaponSelect
===================
*/
void CG_DrawWeaponSelect( void )
{
/*    int		i;
    int		bits;
    int		count;
    int		x, y, w;
    char	*name;
    float	*color;

    // don't display if dead
    if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 )
    {
        return;
    }

    color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME );
    if ( !color )
    {
        return;
    }
    RE_SetColor( color );

    // showing weapon select clears pickup item display, but not the blend blob
    cg.itemPickupTime = 0;

    // count the number of weapons owned
    bits = cg.snap->ps.stats[ STAT_WEAPONS ];
    count = 0;
    for ( i = 1 ; i < 16 ; i++ )
    {
        if ( bits & ( 1 << i ) )
        {
            count++;
        }
    }

    x = 320 - count * 20;
    y = 380;

    for ( i = 1 ; i < 16 ; i++ )
    {
        if ( !( bits & ( 1 << i ) ) )
        {
            continue;
        }

        CG_RegisterWeapon( i );

        // draw weapon icon
        CG_DrawPic( x, y, 32, 32, cg_weapons[i].weaponIcon );

        // draw selection marker
        if ( i == cg.weaponSelect )
        {
            CG_DrawPic( x-4, y-4, 40, 40, cgs.media.selectShader );
        }

        // no ammo cross on top
        if ( !cg.snap->ps.ammo[ i ] )
        {
            CG_DrawPic( x, y, 32, 32, cgs.media.noammoShader );
        }

        x += 40;
    }

    // draw the selected name
    if ( cg_weapons[ cg.weaponSelect ].item )
    {
        name = cg_weapons[ cg.weaponSelect ].item->pickup_name;
        if ( name )
        {
            w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH;
            x = ( SCREEN_WIDTH - w ) / 2;
            CG_DrawBigStringColor(x, y - 22, name, color);
        }
    }

    RE_SetColor( NULL );*/
}

char *weaponNames[] =
{
    "WP_NONE",
    "WP_MELEE",
    "WP_RANGE",
    "WP_SPECIAL",
    "WP_MAX_NUMBER"
};

/*
===============
CG_NextWeapon_f
===============
*/
void CG_NextWeapon_f( void )
{
    if ( !cg.snap )
        return;
    if ( cg.snap->ps.pm_flags & PMF_FOLLOW )
        return;
	if(cg.weaponSelect >= WP_SPECIAL)
		return;

    cg.weaponSelectTime = cg.time;
	cg.weaponSelect++;

    Com_Printf("%s\n", (cg.weaponSelect >= WP_NONE) ? weaponNames[cg.weaponSelect] : "Error");
}

/*
===============
CG_PrevWeapon_f
===============
*/
void CG_PrevWeapon_f( void )
{
    if ( !cg.snap )
        return;
    if ( cg.snap->ps.pm_flags & PMF_FOLLOW )
        return;
	if(cg.weaponSelect <= WP_MELEE)
		return;

	cg.weaponSelectTime = cg.time;
	cg.weaponSelect--;

    Com_Printf("%s\n", (cg.weaponSelect <= WP_NUM_WEAPONS) ? weaponNames[cg.weaponSelect] : "Error");
}

/*
===================================================================================================
 
WEAPON EVENTS
 
===================================================================================================
*/

/*
================
CG_FireWeapon
 
Caused by an EV_FIRE_WEAPON event
================
*/
void CG_FireWeapon( centity_t *cent )
{
/*    entityState_t *ent;
    int				c;
    weaponInfo_t	*weap;

    ent = &cent->currentState;
    if ( ent->weapon == WP_NONE )
    {
        return;
    }
    if ( ent->weapon >= WP_NUM_WEAPONS )
    {
        Com_Error( ERR_DROP,  "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" );
        return;
    }
    weap = &cg_weapons[ ent->weapon ];

    // mark the entity as muzzle flashing, so when it is added it will
    // append the flash to the weapon model
    cent->muzzleFlashTime = cg.time;

    // lightning gun only does this this on initial press
    if ( ent->weapon == WP_LIGHTNING )
    {
        if ( cent->pe.lightningFiring )
        {
            return;
        }
    }

    // play quad sound if needed
    if ( cent->currentState.powerups & ( 1 << PW_QUAD ) )
    {
        S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.media.quadSound );
    }

    // play a sound
    for ( c = 0 ; c < 4 ; c++ )
    {
        if ( !weap->flashSound[c] )
        {
            break;
        }
    }
    if ( c > 0 )
    {
        c = rand() % c;
        if ( weap->flashSound[c] )
        {
            S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound[c] );
        }
    }

    // do brass ejection
    if ( weap->ejectBrassFunc && cg_brassTime->integer > 0 )
    {
        weap->ejectBrassFunc( cent );
    }*/
}


/*
=================
CG_MissileHitWall
 
Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing
=================
*/
void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType )
{
/*    qhandle_t		mod;
    qhandle_t		mark;
    qhandle_t		shader;
    sfxHandle_t		sfx;
    float			radius;
    float			light;
    vec3_t			lightColor;
    localEntity_t	*le;
    int				r;
    qboolean		alphaFade;
    qboolean		isSprite;
    int				duration;
    vec3_t			sprOrg;
    vec3_t			sprVel;

    mark = 0;
    radius = 32;
    sfx = 0;
    mod = 0;
    shader = 0;
    light = 0;
    lightColor[0] = 1;
    lightColor[1] = 1;
    lightColor[2] = 0;

    // set defaults
    isSprite = qfalse;
    duration = 600;

    switch ( weapon )
    {
    default:
    case WP_LIGHTNING:
        // no explosion at LG impact, it is added with the beam
        r = rand() & 3;
        if ( r < 2 )
        {
            sfx = cgs.media.sfx_lghit2;
        }
        else if ( r == 2 )
        {
            sfx = cgs.media.sfx_lghit1;
        }
        else
        {
            sfx = cgs.media.sfx_lghit3;
        }
        mark = cgs.media.holeMarkShader;
        radius = 12;
        break;
    case WP_GRENADE_LAUNCHER:
        mod = cgs.media.dishFlashModel;
        shader = cgs.media.grenadeExplosionShader;
        sfx = cgs.media.sfx_rockexp;
        mark = cgs.media.burnMarkShader;
        radius = 64;
        light = 300;
        isSprite = qtrue;
        break;
    case WP_ROCKET_LAUNCHER:
        mod = cgs.media.dishFlashModel;
        shader = cgs.media.rocketExplosionShader;
        sfx = cgs.media.sfx_rockexp;
        mark = cgs.media.burnMarkShader;
        radius = 64;
        light = 300;
        isSprite = qtrue;
        duration = 1000;
        lightColor[0] = 1;
        lightColor[1] = 0.75;
        lightColor[2] = 0.0;
        if (cg_oldRocket->integer == 0)
        {
            // explosion sprite animation
            VectorMA( origin, 24, dir, sprOrg );
            VectorScale( dir, 64, sprVel );

            CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1400, 20, 30 );
        }
        break;
    case WP_RAILGUN:
        mod = cgs.media.ringFlashModel;
        shader = cgs.media.railExplosionShader;
        sfx = cgs.media.sfx_plasmaexp;
        mark = cgs.media.energyMarkShader;
        radius = 24;
        break;
    case WP_PLASMAGUN:
        mod = cgs.media.ringFlashModel;
        shader = cgs.media.plasmaExplosionShader;
        sfx = cgs.media.sfx_plasmaexp;
        mark = cgs.media.energyMarkShader;
        radius = 16;
        break;
    case WP_BFG:
        mod = cgs.media.dishFlashModel;
        shader = cgs.media.bfgExplosionShader;
        sfx = cgs.media.sfx_rockexp;
        mark = cgs.media.burnMarkShader;
        radius = 32;
        isSprite = qtrue;
        break;
    case WP_SHOTGUN:
        mod = cgs.media.bulletFlashModel;
        shader = cgs.media.bulletExplosionShader;
        mark = cgs.media.bulletMarkShader;
        sfx = 0;
        radius = 4;
        break;
    case WP_MACHINEGUN:
        mod = cgs.media.bulletFlashModel;
        shader = cgs.media.bulletExplosionShader;
        mark = cgs.media.bulletMarkShader;

        r = rand() & 3;
        if ( r == 0 )
        {
            sfx = cgs.media.sfx_ric1;
        }
        else if ( r == 1 )
        {
            sfx = cgs.media.sfx_ric2;
        }
        else
        {
            sfx = cgs.media.sfx_ric3;
        }

        radius = 8;
        break;
    }

    if ( sfx )
    {
        S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx );
    }

    //
    // create the explosion
    //
    if ( mod )
    {
        le = CG_MakeExplosion( origin, dir,
                               mod,	shader,
                               duration, isSprite );
        le->light = light;
        VectorCopy( lightColor, le->lightColor );
        if ( weapon == WP_RAILGUN )
        {
            // colorize with client color
            VectorCopy( cgs.clientinfo[clientNum].color1, le->color );
        }
    }

    //
    // impact mark
    //
    alphaFade = (mark == cgs.media.energyMarkShader);	// plasma fades alpha, all others fade color
    if ( weapon == WP_RAILGUN )
    {
        float	*color;

        // colorize with client color
        color = cgs.clientinfo[clientNum].color2;
        CG_ImpactMark( mark, origin, dir, random()*360, color[0],color[1], color[2],1, alphaFade, radius, qfalse );
    }
    else
    {
        CG_ImpactMark( mark, origin, dir, random()*360, 1,1,1,1, alphaFade, radius, qfalse );
    }*/
}


/*
=================
CG_MissileHitPlayer
=================
*/
void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum )
{
/*    CG_Bleed( origin, entityNum );

    // some weapons will make an explosion with the blood, while
    // others will just make the blood
    switch ( weapon )
    {
    case WP_GRENADE_LAUNCHER:
    case WP_ROCKET_LAUNCHER:
        CG_MissileHitWall( weapon, 0, origin, dir, IMPACTSOUND_FLESH );
        break;
    default:
        break;
    }*/
}

/*
============================================================================
 
SHOTGUN TRACING
 
============================================================================
*/

/*
================
CG_ShotgunPellet
================
*/
static void CG_ShotgunPellet( vec3_t start, vec3_t end, int skipNum )
{
    trace_t		tr;
    int sourceContentType, destContentType;

    CG_Trace( &tr, start, NULL, NULL, end, skipNum, MASK_SHOT );

    sourceContentType = CM_PointContents( start, 0 );
    destContentType = CM_PointContents( tr.endpos, 0 );

    // FIXME: should probably move this cruft into CG_BubbleTrail
    if ( sourceContentType == destContentType )
    {
        if ( sourceContentType & CONTENTS_WATER )
        {
            CG_BubbleTrail( start, tr.endpos, 32 );
        }
    }
    else if ( sourceContentType & CONTENTS_WATER )
    {
        trace_t trace;

        CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER, qfalse );
        CG_BubbleTrail( start, trace.endpos, 32 );
    }
    else if ( destContentType & CONTENTS_WATER )
    {
        trace_t trace;

        CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER, qfalse );
        CG_BubbleTrail( tr.endpos, trace.endpos, 32 );
    }

    if (  tr.surfaceFlags & SURF_NOIMPACT )
    {
        return;
    }

    if ( cg_entities[tr.entityNum].currentState.eType == ET_PLAYER )
    {
//        CG_MissileHitPlayer( WP_SHOTGUN, tr.endpos, tr.plane.normal, tr.entityNum );
    }
    else
    {
        if ( tr.surfaceFlags & SURF_NOIMPACT )
        {
            // SURF_NOIMPACT will not make a flame puff or a mark
            return;
        }
        if ( tr.surfaceFlags & SURF_METALSTEPS )
        {
//            CG_MissileHitWall( WP_SHOTGUN, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL );
        }
        else
        {
//            CG_MissileHitWall( WP_SHOTGUN, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT );
        }
    }
}

/*
================
CG_ShotgunPattern
 
Perform the same traces the server did to locate the
hit splashes
================
*/
static void CG_ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, int otherEntNum )
{
    int			i;
    float		r, u;
    vec3_t		end;
    vec3_t		forward, right, up;

    // derive the right and up vectors from the forward vector, because
    // the client won't have any other information
    VectorNormalize2( origin2, forward );
    PerpendicularVector( right, forward );
    CrossProduct( forward, right, up );

    // generate the "random" spread pattern
    for ( i = 0 ; i < DEFAULT_SHOTGUN_COUNT ; i++ )
    {
        r = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16;
        u = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16;
        VectorMA( origin, 8192 * 16, forward, end);
        VectorMA (end, r, right, end);
        VectorMA (end, u, up, end);

        CG_ShotgunPellet( origin, end, otherEntNum );
    }
}

/*
==============
CG_ShotgunFire
==============
*/
void CG_ShotgunFire( entityState_t *es )
{
    vec3_t	v;
    int		contents;

    VectorSubtract( es->origin2, es->pos.trBase, v );
    VectorNormalize( v );
    VectorScale( v, 32, v );
    VectorAdd( es->pos.trBase, v, v );
    if ( cgs.glconfig.hardwareType != GLHW_RAGEPRO )
    {
        // ragepro can't alpha fade, so don't even bother with smoke
        vec3_t			up;

        contents = CM_PointContents( es->pos.trBase, 0 );
        if ( !( contents & CONTENTS_WATER ) )
        {
            VectorSet( up, 0, 0, 8 );
            CG_SmokePuff( v, up, 32, 1, 1, 1, 0.33f, 900, cg.time, 0, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader );
        }
    }
    CG_ShotgunPattern( es->pos.trBase, es->origin2, es->eventParm, es->otherEntityNum );
}

/*
============================================================================
 
BULLETS
 
============================================================================
*/


/*
===============
CG_Tracer
===============
*/
void CG_Tracer( vec3_t source, vec3_t dest )
{
    vec3_t		forward, right;
    polyVert_t	verts[4];
    vec3_t		line;
    float		len, begin, end;
    vec3_t		start, finish;
    vec3_t		midpoint;

    // tracer
    VectorSubtract( dest, source, forward );
    len = VectorNormalize( forward );

    // start at least a little ways from the muzzle
    if ( len < 100 )
    {
        return;
    }
    begin = 50 + random() * (len - 60);
    end = begin + cg_tracerLength->value;
    if ( end > len )
    {
        end = len;
    }
    VectorMA( source, begin, forward, start );
    VectorMA( source, end, forward, finish );

    line[0] = DotProduct( forward, cg.refdef.viewaxis[1] );
    line[1] = DotProduct( forward, cg.refdef.viewaxis[2] );

    VectorScale( cg.refdef.viewaxis[1], line[1], right );
    VectorMA( right, -line[0], cg.refdef.viewaxis[2], right );
    VectorNormalize( right );

    VectorMA( finish, cg_tracerWidth->value, right, verts[0].xyz );
    verts[0].st[0] = 0;
    verts[0].st[1] = 1;
    verts[0].modulate[0] = 255;
    verts[0].modulate[1] = 255;
    verts[0].modulate[2] = 255;
    verts[0].modulate[3] = 255;

    VectorMA( finish, -cg_tracerWidth->value, right, verts[1].xyz );
    verts[1].st[0] = 1;
    verts[1].st[1] = 0;
    verts[1].modulate[0] = 255;
    verts[1].modulate[1] = 255;
    verts[1].modulate[2] = 255;
    verts[1].modulate[3] = 255;

    VectorMA( start, -cg_tracerWidth->value, right, verts[2].xyz );
    verts[2].st[0] = 1;
    verts[2].st[1] = 1;
    verts[2].modulate[0] = 255;
    verts[2].modulate[1] = 255;
    verts[2].modulate[2] = 255;
    verts[2].modulate[3] = 255;

    VectorMA( start, cg_tracerWidth->value, right, verts[3].xyz );
    verts[3].st[0] = 0;
    verts[3].st[1] = 0;
    verts[3].modulate[0] = 255;
    verts[3].modulate[1] = 255;
    verts[3].modulate[2] = 255;
    verts[3].modulate[3] = 255;

    RE_AddPolyToScene( cgs.media.tracerShader, 4, verts, 1 );

    midpoint[0] = ( start[0] + finish[0] ) * 0.5;
    midpoint[1] = ( start[1] + finish[1] ) * 0.5;
    midpoint[2] = ( start[2] + finish[2] ) * 0.5;

    // add the tracer sound
    S_StartSound( midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound );

}


/*
======================
CG_CalcMuzzlePoint
======================
*/
static qboolean	CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle )
{
    vec3_t		forward;
    centity_t	*cent;
    int			anim;

    if ( entityNum == cg.snap->ps.clientNum )
    {
        VectorCopy( cg.snap->ps.origin, muzzle );
        muzzle[2] += cg.snap->ps.viewheight;
        AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL );
        VectorMA( muzzle, 14, forward, muzzle );
        return qtrue;
    }

    cent = &cg_entities[entityNum];
    if ( !cent->currentValid )
    {
        return qfalse;
    }

    VectorCopy( cent->currentState.pos.trBase, muzzle );

    AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL );
    anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT;
    if ( anim == LEGS_WALKCR || anim == LEGS_IDLECR )
    {
        muzzle[2] += CROUCH_VIEWHEIGHT;
    }
    else
    {
        muzzle[2] += DEFAULT_VIEWHEIGHT;
    }

    VectorMA( muzzle, 14, forward, muzzle );

    return qtrue;

}

/*
======================
CG_Bullet
 
Renders bullet effects.
======================
*/
void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum )
{
    trace_t trace;
    int sourceContentType, destContentType;
    vec3_t		start;

    // if the shooter is currently valid, calc a source point and possibly
    // do trail effects
    if ( sourceEntityNum >= 0 && cg_tracerChance->value > 0 )
    {
        if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) )
        {
            sourceContentType = CM_PointContents( start, 0 );
            destContentType = CM_PointContents( end, 0 );

            // do a complete bubble trail if necessary
            if ( ( sourceContentType == destContentType ) && ( sourceContentType & CONTENTS_WATER ) )
            {
                CG_BubbleTrail( start, end, 32 );
            }
            // bubble trail from water into air
            else if ( ( sourceContentType & CONTENTS_WATER ) )
            {
                CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER, qfalse );
                CG_BubbleTrail( start, trace.endpos, 32 );
            }
            // bubble trail from air into water
            else if ( ( destContentType & CONTENTS_WATER ) )
            {
                CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER, qfalse );
                CG_BubbleTrail( trace.endpos, end, 32 );
            }

            // draw a tracer
            if ( random() < cg_tracerChance->value )
            {
                CG_Tracer( start, end );
            }
        }
    }

    // impact splash and mark
    if ( flesh )
    {
        CG_Bleed( end, fleshEntityNum );
    }
    else
    {
//        CG_MissileHitWall( WP_MACHINEGUN, 0, end, normal, IMPACTSOUND_DEFAULT );
    }

}
